﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Windows.Forms;
using aboveXWords.Properties;

namespace aboveXWords.GUI
{
  public class Arrow : List<PointF>
  {
    public Square From { get; set; }
    public Square To { get; set; }

    public void Paint(Graphics graphics)
    {
      if (From == null) return;
      var capPen = new Pen(Settings.Default.ArrowColor)
                     {
                       CustomEndCap = new AdjustableArrowCap(5, 2, true),
                       Width = 5,
                       DashStyle = DashStyle.Solid
                     };
      graphics.DrawPath(capPen, CreateGraphicsPath());
    }

    private GraphicsPath CreatePath()
    {
      var path = new GraphicsPath();
      if (From == null) return path;
      //List<PointF> points = new List<PointF> { From.Center };
      //points.AddRange(ToArray());
      //points.Add(To.Center);
      //path.AddLines(points.ToArray());
      return path;
    }

    public void Move(MouseEventArgs mouseEventArgs, MouseEventArgs mouseDownPosition)
    {
      Snap(mouseEventArgs.Location.X - mouseDownPosition.X, mouseEventArgs.Location.X - mouseDownPosition.Y);
    }

    internal void Snap(float deltaX, float deltaY)
    {
      if (Count == 0) return;
      List<PointF> points = this.Select(point => new PointF(point.X + deltaX, deltaY)).ToList();
      RemoveRange(0, Count);
      AddRange(points);
    }

    public void Invalidate(Canvas canvas)
    {
      canvas.InvalidateCanvas(CreatePath().GetBounds());
    }

    protected internal GraphicsPath CreateGraphicsPath()
    {
      var path = new GraphicsPath();
      PointF startPoint = From.Center;
      PointF endPoint = To.Center;
      if (Count == 0)
        AddLine(path, startPoint, endPoint);
      else
      {
        int lastHandle = Count - 1;
        double x0 = startPoint.X;
        double y0 = startPoint.Y;
        double x1 = this[0].X;
        double y1 = this[0].Y;
        if (!From.LineClip(true, ref x0, ref y0, ref x1, ref y1)) return path;
        var startX = (float) x0;
        var startY = (float) y0;
        path.AddLine(new PointF(startX, startY), this[0]);

        // Line segments inbetween
        for (int i = 0; i < lastHandle; ++i)
          path.AddLine(this[i], this[i + 1]);

        // To stop state
        x0 = this[lastHandle].X;
        y0 = this[lastHandle].Y;
        x1 = endPoint.X;
        y1 = endPoint.Y;
        if (!To.LineClip(false, ref x0, ref y0, ref x1, ref y1)) return path;
        var endX = (float) x0;
        var endY = (float) y0;
        path.AddLine(this[lastHandle], new PointF(endX, endY));
      }
      return path;
    }

    private void AddLine(GraphicsPath path, PointF startPoint, PointF endPoint)
    {
      double x0 = startPoint.X;
      double y0 = startPoint.Y;
      double x1 = endPoint.X;
      double y1 = endPoint.Y;
      if (!To.LineClip(false, ref x0, ref y0, ref x1, ref y1)) return;
      var end = new PointF((float) x0, (float) y0);
      x0 = startPoint.X;
      y0 = startPoint.Y;
      x1 = endPoint.X;
      y1 = endPoint.Y;
      if (!From.LineClip(true, ref x0, ref y0, ref x1, ref y1)) return;
      if (x0 == end.X && y0 == end.Y)
      {
        var capPen = new Pen(Settings.Default.ArrowColor)
        {
          CustomEndCap = new AdjustableArrowCap(4, 3, true),
          Width = 5,
          DashStyle = DashStyle.Solid
        };
        AdjustableArrowCap cap = (AdjustableArrowCap) capPen.CustomEndCap;
        float x = startPoint.X - endPoint.X;
        
        if (Math.Abs(x) < Settings.Default.SquareSize.Width)
          path.AddLine(new PointF(end.X, end.Y ),
                       new PointF(end.X, end.Y - Math.Sign(startPoint.Y - endPoint.Y) * 10));
        else
          path.AddLine(new PointF(end.X, end.Y),
                       new PointF(end.X + 10, end.Y));
      }
      else
        path.AddLine(new PointF((float)x0, (float)y0), end);
    }
  }
}